/*******************************************************************************
 * CLI - A simple command line interface.
 * Copyright (C) 2016-2021 Daniele Pallastrelli
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ******************************************************************************/

#ifndef CLI_DETAIL_WINKEYBOARD_H_
#define CLI_DETAIL_WINKEYBOARD_H_

#include <functional>
#include <string>
#include <thread>
#include <memory>
#include <conio.h>
#include <cassert>

#include "inputdevice.h"

#if !defined(NOMINMAX)
#define NOMINMAX 1 // prevent windows from defining min and max macros
#endif // !defined(NOMINMAX)
#include <windows.h>

namespace cli
{
namespace detail
{

class InputSource
{
public:

    InputSource()
    {
        events[0] = CreateEvent(nullptr, FALSE, FALSE, nullptr); // Obtain a Windows handle to use to stop
        events[1] = GetStdHandle(STD_INPUT_HANDLE); // Get a Windows handle to the keyboard input
    }

    void WaitKbHit()
    {
        // Wait for either the timer to expire or a key press event
        DWORD dwResult = WaitForMultipleObjects(2, events, false, INFINITE);

        if (dwResult == WAIT_FAILED)
        {
            // TODO
            assert(false);
        }
        else
        {
            if (dwResult == WAIT_OBJECT_0) // WAIT_OBJECT_0 corresponds to the stop event
            {
                throw std::runtime_error("InputSource stop");
            }
            else
            {
                return;
            }
        }

        // we can't reach this point
        assert(false);
    }

    void Stop()
    {
        SetEvent(events[0]);
    }

private:
    HANDLE events[2];
};

//

class WinKeyboard : public InputDevice
{
public:
    explicit WinKeyboard(Scheduler& _scheduler) :
        InputDevice(_scheduler),
        servant([this]() noexcept { Read(); })
    {
    }
    ~WinKeyboard() override
    {
        is.Stop();
        servant.join();
    }

private:

    void Read() noexcept
    {
        try
        {
            while (true)
            {
                auto k = Get();
                Notify(k);
            }
        }
        catch (const std::exception&)
        {
            // nothing to do: just exit
        }
    }

    std::pair<KeyType, char> Get()
    {
        is.WaitKbHit();

        int c = _getch();
        switch (c)
        {
            case EOF:
            case 4:  // EOT ie CTRL-D
            case 26: // CTRL-Z
            case 3:  // CTRL-C
                return std::make_pair(KeyType::eof, ' ');
                break;

            case 224: // symbol
            {
                c = _getch();
                switch (c)
                {
                    case 72: return std::make_pair(KeyType::up, ' ');
                    case 80: return std::make_pair(KeyType::down, ' ');
                    case 75: return std::make_pair(KeyType::left, ' ');
                    case 77: return std::make_pair(KeyType::right, ' ');
                    case 71: return std::make_pair(KeyType::home, ' ');
                    case 79: return std::make_pair(KeyType::end, ' ');
                    case 83: return std::make_pair(KeyType::canc, ' ');
                    default: return std::make_pair(KeyType::ignored, ' ');
                }
            }
            case 8:
                return std::make_pair(KeyType::backspace, c);
                break;
            case 12: // CTRL-L
                return std::make_pair(KeyType::clear, ' ');
                break;
            case 13:
                return std::make_pair(KeyType::ret, c);
                break;
            default: // hopefully ascii
            {
                const char ch = static_cast<char>(c);
                return std::make_pair(KeyType::ascii, ch);
            }
        }
        return std::make_pair(KeyType::ignored, ' ');
    }

    InputSource is;
    std::thread servant;
};

} // namespace detail
} // namespace cli

#endif // CLI_DETAIL_WINKEYBOARD_H_
